package com.elminster.common.util;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * File Utilities
 * 
 * @author Gu
 * @version 1.0
 *
 */
public abstract class FileUtil {
	
	/** File separate in Windows System: <code>\\</code> */
	public static final String WINDOW_FILE_SEPARATE = "\\\\";
	/** File separate in Linux/Unix System: <code>/</code> */
	public static final String LINUX_FILE_SEPARATE = "/";
	/** Buffer size in file copy */
	public static final int BUFF_SIZE = 1024;
	/** Base folder path */
	public static final String BASE = new File(".").getAbsolutePath() + LINUX_FILE_SEPARATE;

	/**
	 * Create folder using the specified absolute file path (create the parent folder if necessary)
	 * @param fileName　the absolute file path
	 */
	public static void createFolder(String fileName) {
		if (null == fileName) {
			return;
		}
		String name = replaceFileSeparate(fileName);
		boolean isEndedBySeparate = false;
		// whether end with "/" 
		if (name.endsWith(LINUX_FILE_SEPARATE)) {
			isEndedBySeparate = true;
		}
		File f;
		//　split the full file name with "/"
		String[] splited = name.split(LINUX_FILE_SEPARATE);
		int length = splited.length;
		String folder = "";
		// ignore the last character if it was "/"
		length = isEndedBySeparate ? length : length - 1;
		//　create the parent folder if necessary
		for (int i = 0; i < length; i++) {
			folder += splited[i] + LINUX_FILE_SEPARATE;
			f = new File(folder);
			if (!f.exists() || !f.isDirectory()) {
				f.mkdir();
			}
		}
	}
	
	/**
	 * Move the specified file to another file (create folder for move to file if necessary)
	 * @param srcFile　the file to move
	 * @param destFile　the file to move to
	 * @throws Exception　Exception
	 */
	public static void moveFile(String srcFile, String destFile) throws Exception {
		if (isFileExist(srcFile)) {
			throw new IllegalArgumentException("Source file is NOT exist.");
		}
		File src = new File(srcFile);
		createFolder(destFile);
		File dest = new File(destFile);
		src.renameTo(dest);
	}
	
	/**
	 * Copy the specified file to another file (create folder for move to file if necessary)
	 * @param srcFile　the file to copy
	 * @param destFile　the file to move to
	 * @throws Exception　Exception
	 */
	public static void copyFile(String srcFile, String destFile) throws Exception {
		if (StringUtil.isEmpty(srcFile)) {
			throw new IllegalArgumentException("Source file cannot be null.");
		}
		File file = new File(srcFile);
		if (!file.exists()) {
			throw new IllegalArgumentException("Source file is not exist.");
		}
		if (file.isDirectory()) {
			throw new IllegalArgumentException("Source file is exist, but it is a directory.");
		}
		if (StringUtil.isEmpty(destFile)) {
			throw new IllegalArgumentException("Dest file cannot be null");
		}
		createFolder(destFile);
		InputStream in = null;
		OutputStream out = null;
		try {
			in = new FileInputStream(srcFile);
			out = new FileOutputStream(destFile);
			byte[] buffer = new byte[BUFF_SIZE];
			int n = 0;
			while (-1 != (n = in.read(buffer))) {
				out.write(buffer, 0, n);
			}
			out.flush();
		} finally {
			if (null != out) {
				out.close();
			}
			if (null != in) {
				in.close();
			}
		}
	}
	
	/**
	 * Fix the specified folder name what should end with a file separate
	 * @param folderName　the specified folder name
	 * @return　the folder name which end with a file separate
	 */
	public static String fixFolderName(String folderName) throws Exception {
		if (StringUtil.isEmpty(folderName)) {
			throw new IllegalArgumentException("Folder name file cannot be null");
		}
		String rst = replaceFileSeparate(folderName);
		if (rst.endsWith(LINUX_FILE_SEPARATE)) {
			return rst;
		} else {
			return rst + LINUX_FILE_SEPARATE;
		}
	}
	
	/**
	 * Copy the specified file to a folder (create the copy to folder if necessary)
	 * @param srcFile　the file to copy
	 * @param destFolder　the copy to folder
	 * @throws Exception　Exception
	 */
	public static void copyFile2Directory(String srcFile, String destFolder) throws Exception {
		if (StringUtil.isEmpty(srcFile)) {
			throw new IllegalArgumentException("Source file cannot be null.");
		}
		File file = new File(srcFile);
		if (!file.exists()) {
			throw new IllegalArgumentException("Source file is not exist.");
		}
		if (file.isDirectory()) {
			throw new IllegalArgumentException("Source file is exist, but it is a directory.");
		}
		if (StringUtil.isEmpty(destFolder)) {
			throw new IllegalArgumentException("Dest folder cannot be null");
		}
		String fixedFolderName = fixFolderName(destFolder);
		createFolder(fixedFolderName);
		String destFileName = fixedFolderName + getFileName(srcFile);
		InputStream in = null;
		OutputStream out = null;
		try {
			in = new FileInputStream(srcFile);
			out = new FileOutputStream(destFileName);
			int n = 0;
			byte[] buffer = new byte[BUFF_SIZE];
			while (-1 != (n = in.read(buffer))) {
				out.write(buffer, 0, n);
			}
			out.flush();
		} finally {
			if (null != out) {
				out.close();
			}
			if (null != in) {
				in.close();
			}
		}
	}
	
	/**
	 * Copy the specified folder to an folder (create the copy to folder if necessary)
	 * @param src　the folder to copy
	 * @param dest　the copy to folder
	 * @throws Exception　Exception
	 */
	public static void copyDirectory(String srcFolder, String destFolder) throws Exception {
		if (StringUtil.isEmpty(srcFolder)) {
			throw new IllegalArgumentException("Source folder cannot be null.");
		}
		File file = new File(srcFolder);
		if (!file.exists()) {
			throw new IllegalArgumentException("Source folder is not exist.");
		}
		if (file.isFile()) {
			throw new IllegalArgumentException("Source folder is exist, but it is a file.");
		}
		if (StringUtil.isEmpty(destFolder)) {
			throw new IllegalArgumentException("Dest folder cannot be null.");
		}
		createFolder(destFolder);
		File[] srcFiles = file.listFiles();
		int length = srcFiles.length;
		for (int i = 0; i < length; i++) {
			File f = srcFiles[i];
			copyFile2Directory(f.getAbsolutePath(), destFolder);
		}
	}
	
	/**
	 * Convert all file separate of the specified file name to "/"
	 * @param fileName　 the specified file name
	 * @return　converted file name
	 */
	public static String replaceFileSeparate(String fileName) {
		if (null == fileName) {
			return null;
		}
		return fileName.replaceAll(WINDOW_FILE_SEPARATE, LINUX_FILE_SEPARATE);
	}
	
	/**
     * Get the folder name of the specified absolute path
     * @param path　the specified absolute path
     * @return the folder name
     */
    public static String getPath(String path) {

        String value = null;
        File file = new File(path);
        value = file.getParentFile().getAbsolutePath();

        return value;
    }

    /**
     * Get the file name of the specified absolute path
     * @param path　the specified absolute path
     * @return the file name
     */
    public static String getFileName(String path) {

    	String fileName = "";
    	File file = new File(path);

    	if (file.isFile()) {
    		fileName = file.getName();
    	}
    	return fileName;
    }
    
    /**
     * Get the file extension of the specified absolute path
     * @param path　the specified absolute path
     * @return　the file extension
     */
    public static String getFileExtension(String path) {
    	if (StringUtil.isEmpty(path)) {
    		return "";
    	}
    	int idx = path.lastIndexOf(".");
    	if (-1 == idx) {
    		return "";
    	}
    	return path.substring(idx + 1);
    }

    /**
     * Get the file name of the specified absolute path without extension
     * @param path　the specified absolute path
     * @return the file name without extension
     */
    public static String getFileNameExcludeExtension(String path) {

        String fileName = null;
        File file = new File(path);

        if (file.isFile()) {
            fileName = path.substring(getPath(path).length());
            fileName = fileName.substring(0, fileName.lastIndexOf("."));
            if (fileName.startsWith(System.getProperty("file.separator"))) {
                fileName = fileName.substring(1);
            }
        }
        return fileName;
    }
    
    /**
     * Delete the specified folder unless it's empty
     * @param folderName　the specified folder
     */
    public static void deleteEmptyFolder(String folderName) {
    	File folder = new File(folderName);
    	if (!folder.exists()) {
            return;
        }
    	folder.delete();
    }

    /**
     * Delete the specified file or folder (folder contains all sub folder and sub file)
     * @param filename the specified absolute path
     */
    public static void deleteFile(String filename) throws Exception {
    	File folder = new File(filename);
    	if (!folder.exists()) {
            return;
        }
    	if (folder.isDirectory()) {
    		File[] list = folder.listFiles();
    		for (File f : list) {
    			deleteFile(f.getAbsolutePath());
    		}
    		folder.delete();
    	} else if (folder.isFile()) {
    		folder.delete();
    	}
    }
    
    /**
     * Get all sub file name in the specified folder in sort
     * @param folderPath the specified folder
     * @return all sub file name in the specified folder in sort
     * @throws Exception Exception
     */
    public static List<String> getFileNameList(String folderPath) {

        List<String> fileList = null;
        File d = new File(folderPath);
        File[] list = d.listFiles();

        if (list.length > 0) {
            fileList = new ArrayList<String>();
            int length = list.length;
            for (int i = 0; i < length; i++) {
                if (list[i].isFile()) {
                    fileList.add(list[i].getName());
                }
            }
            Collections.sort(fileList);
        }
        return fileList;
    }
    
    /**
     * Create a zip file
     * @param outputZipFileName　output zip file name
     * @param files　input zip file(s)
     * @throws Exception　Exception
     */
    public static void createZip(String outputZipFileName, String... files) throws Exception {
    	if (null == outputZipFileName || null == files) {
    		throw new IllegalArgumentException("Parameters cannot be null");
    	}
    	int fileCount = files.length;
    	ZipOutputStream out = null;
    	FileOutputStream fout = null;
    	try {
    		fout = new FileOutputStream(outputZipFileName);
    		out = new ZipOutputStream(fout);
    		for (int i = 0; i < fileCount; i++) {
    			File f = new File(files[i]);
    			zip(out, f, "");
    		}
    		out.flush();
    	} finally {
    		if (null != out) {
    			out.close();
    		}
    		if (null != fout) {
    			fout.close();
    		}
    	}
    }
    
    /**
     * Zip a file
     * @param zOut ZipOutputStream
     * @param file input file
     * @param label　label
     * @throws Exception　Exception
     */
    private static void zip(ZipOutputStream zOut, File file, String label) throws Exception {
    	if (null == zOut || null == file) {
    		throw new IllegalArgumentException("Parameter cannot be null");
    	}
    	FileInputStream fin = null;
    	try {
    		if (file.isDirectory()) {
    			label += file.getName() + LINUX_FILE_SEPARATE;
    			ZipEntry zipEntry = new ZipEntry(label);
    			zOut.putNextEntry(zipEntry);
    			int childrenCount = file.listFiles().length;
    			for (int i = 0; i < childrenCount; i++) {
    				File child = file.listFiles()[i];
    				zip(zOut, child, label);
    			}
    		} else {
    			label += file.getName();
    			ZipEntry zipEntry = new ZipEntry(label);
    			zOut.putNextEntry(zipEntry);
    			fin = new FileInputStream(file);
    			byte[] buff = new byte[BUFF_SIZE];
    			while (-1 != fin.read(buff)) {
    				zOut.write(buff);
    			}
    			zOut.flush();
    		}
    	} finally {
    		if (null != fin) {
    			fin.close();
    		}
    		if (null != zOut) {
    			zOut.closeEntry();
    		}
    	}
    }
    
    /**
     * Unzip a zip file
     * @param zipFile　zip file path
     * @param outputFile　output file
     * @throws Exception　Exception
     */
    public static void unzip(String zipFile, String outputFile) throws Exception {
    	if (null == zipFile || null == outputFile) {
    		throw new IllegalArgumentException("Parameter cannot be null");
    	}
    	ZipInputStream zin = null;
    	FileInputStream fin = null;
    	FileOutputStream fout = null;
    	try {
    		fin = new FileInputStream(zipFile);
    		zin = new ZipInputStream(fin);
    		String base = fixFolderName(outputFile);
    		createFolder(base);
    		ZipEntry zipEntry = null;
    		while (null != (zipEntry = zin.getNextEntry())) {
    			String file = base + zipEntry.getName();
    			if (zipEntry.isDirectory()) {
    	    		createFolder(fixFolderName(file));
    	    	} else {
    	    		createFolder(file);
    	    		fout = new FileOutputStream(file);
    	    		byte[] buff = new byte[BUFF_SIZE];
    	    		int count = 0;
    	    		while (-1 != (count = zin.read(buff))) {
    	    			fout.write(buff, 0, count);
    	    		}
    	    		fout.flush();
    	    	}
    			zin.closeEntry();
    		}
    	} finally {
    		if (null != zin) {
    			zin.close();
    		}
    		if (null != fin) {
    			fin.close();
    		}
    		if (null != fout) {
    			fout.close();
    		}
    	}
    }
    
    /**
     * Get the relative path from a specified absolute path in the base path
     * @param fullPath　a specified absolute path
     * @param basePath　the base path
     * @return　the relative path
     * @throws Exception　Exception
     */
    public static String getRelativePath(String fullPath, String basePath) throws Exception {
    	String relativePath = "";
    	
    	fullPath = new File(fullPath).getCanonicalPath();
    	basePath = new File(basePath).getCanonicalPath();
    	
    	String[] splitPath = replaceFileSeparate(fullPath).split(LINUX_FILE_SEPARATE);
    	String[] splitBasePath = replaceFileSeparate(basePath).split(LINUX_FILE_SEPARATE);
    	
    	int length = splitBasePath.length;
    	int splitLength = splitPath.length;
    	int i = 0;
    	for (; i < length; i++) {
    		if (splitPath[i].equalsIgnoreCase(splitBasePath[i])) {
    			continue;
    		} else {
    			break;
    		}
    	}
    	
    	if (i >= length) {
    		if (splitLength >= i) {
    			for (int j = i; j < splitLength; j++) {
    				relativePath += splitPath[j] + LINUX_FILE_SEPARATE;
    			}
    			relativePath = relativePath.substring(0, relativePath.length() - 1);
    		}
    	} else {
    		int remain = length - i;
    		for (int j = 0; j < remain; j++) {
    			relativePath += "../";
    		}
    		for (int j = i; j < splitLength; j++) {
				relativePath += splitPath[j] + LINUX_FILE_SEPARATE;
			}
    		relativePath = relativePath.substring(0, relativePath.length() - 1);
    	}
    	return "./" + relativePath;
    }
    
    /**
     * Check whether the specified file is exist
     * @param filePath　the absolute file path
     * @return　whether the file is exist
     */
    public static boolean isFileExist(String filePath) {
    	if (StringUtil.isEmpty(filePath)) {
    		return false;
    	}
    	File file = new File(filePath);
    	if (file.exists() && file.isFile()) {
    		return true;
    	}
    	return false;
    }
    
    /**
     * Check whether the specified folder is exist
     * @param folderPath　the absolute folder path
     * @return　whether the folder is exist
     */
    public static boolean isFolderExist(String folderPath) {
    	if (StringUtil.isEmpty(folderPath)) {
    		return false;
    	}
    	File file = new File(folderPath);
    	if (file.exists() && file.isDirectory()) {
    		return true;
    	}
    	return false;
    }
    
    /**
     * Read a specified file to a list by line (ignore the blank line)
     * @param fileName　a specified file
     * @param charset the charset of the specified file
     * @return　the list contains all the file contexts
     * @throws Exception　Exception
     */
    public static List<String> readFileByLine(String fileName, String charset) throws Exception {
    	return readFileByLine(fileName, true, charset);
    }
    
    /**
     * Read a specified file to a list by line
     * @param fileName　a specified file
     * @param skipBlankLine　whether ignore the blank line
     * @param charset the charset of the specified file
     * @return　the list contains all the file contexts
     * @throws Exception　Exception
     */
    public static List<String> readFileByLine(String fileName, boolean skipBlankLine, String charset) throws Exception {
    	List<String> lines = new ArrayList<String>();
    	InputStream is = null;
    	Reader isReader = null;
    	BufferedReader br = null;
    	try {
			is = new FileInputStream(fileName);
			isReader = new InputStreamReader(is, charset);
			br = new BufferedReader(isReader);
			
			String line = null;
			while (null != (line = br.readLine())) {
				if (skipBlankLine && StringUtil.isEmpty(line)) {
					continue;
				}
				lines.add(line);
			}
			
			return lines;
		} finally {
			if (null != br) {
				br.close();
			}
			if (null != isReader) {
				isReader.close();
			}
			if (null != is) {
				is.close();
			}
		}
    }
    
    /**
     * Write out a file with the specified bytes
     * @param bytes　the specified bytes
     * @param fileName　output file path
     * @throws Exception　Exception
     */
    public static void write2file(byte[] bytes, String fileName) throws Exception {
    	OutputStream os = null;
    	try {
    		os = new FileOutputStream(fileName);
    		os.write(bytes);
    		os.flush();
    	} finally {
    		if (null != os) {
    			os.close();
    		}
    	}
    }
}
